package com.yesallchina.wxapp.officialaccount.utils;


import com.yesallchina.wxapp.officialaccount.config.WxOfficialAccountCommonProperties;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.*;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class WxHttpUtils {

    private static Logger log = LoggerFactory.getLogger(WxHttpUtils.class);

    /**
     * 请求响应编码
     */
    private static final String UTF8_CHARSET = "UTF-8";

    /**
     * 最大连接数
     */
    private static final int maxConnectCount = 100;

    /**
     * 最大请求重试次数
     */
    private static final int maxRequestRetryCount = 3;

    private static Map<String, ClientConfig> mConfig = new HashMap();

    private final Lock lock;

    private static WxHttpUtils exec = new WxHttpUtils();

    private WxHttpUtils() {
        this.lock = new ReentrantLock();
    }


    private ClientConfig init(WxOfficialAccountCommonProperties wxConfig) {
        String appId = wxConfig.getAppid();
        if (!StringUtils.hasText(appId)){
            appId = "_default";
        }
        if (mConfig.get(appId) == null) {
            exec.lock.lock();
            try {
                if (mConfig.get(appId) == null) {
                    mConfig.put(appId, config(wxConfig));
                }
            } finally {
                exec.lock.unlock();
            }
        }
        return mConfig.get(appId);
    }

    private ClientConfig config(WxOfficialAccountCommonProperties wxConfig) {
        // 配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(10 * 1000)
                .setConnectTimeout(10 * 1000)
                .setSocketTimeout(15 * 1000)
                .build();
        SSLConnectionSocketFactory sslsf = null;
        log.info("初始化HTTP，appId = [{}] , payCertPassword = [{}]" , wxConfig.getAppid(),wxConfig.getPayCertPassword());
        log.info("微信支付密钥文件信息:[{}]",wxConfig.getPayCertFileAbsPath());
        String certPassword = wxConfig.getPayCertPassword();
        String certFile = wxConfig.getPayCertFileAbsPath();
        try {
            if (StringUtils.hasText(certPassword) && StringUtils.hasText(certFile)) {
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                FileInputStream instream = new FileInputStream(new File(certFile));
                try {
                    keyStore.load(instream, certPassword.toCharArray());
                } finally {
                    instream.close();
                }
                SSLContext sslcontext = SSLContexts.custom()
                        .loadKeyMaterial(keyStore, certPassword.toCharArray())
                        .build();
                sslsf = new SSLConnectionSocketFactory(
                        sslcontext,
                        new String[]{"TLSv1"},
                        null,
                        SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (sslsf == null) {
            sslsf = SSLConnectionSocketFactory.getSocketFactory();
        }
        ConnectionSocketFactory httpsf = PlainConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", httpsf)
                .register("https", sslsf)
                .build();

        PoolingHttpClientConnectionManager pcm = new PoolingHttpClientConnectionManager(registry);
        // 设置最大连接数
        pcm.setMaxTotal(maxConnectCount);
        // 将每个路由基础的连接增加
        pcm.setDefaultMaxPerRoute(maxConnectCount);

        // 请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception,
                                        int executionCount, HttpContext context) {
                if (executionCount >= maxRequestRetryCount) {
                    log.error("重试了{}次之后放弃", maxRequestRetryCount);
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {
                    log.warn("服务器连接丢失，执行重试");
                    return true;
                }
                if (exception instanceof SSLHandshakeException || exception instanceof SSLException) {
                    log.error("SSL安全连接建立失败");
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    log.error("连接意外中断或者被拒绝,执行重试");
                    return true;
                }
                if (exception instanceof UnknownHostException) {
                    log.error("请求地址不存在");
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    log.error("连接超时");
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return false;
                }
                return false;
            }
        };
        return new ClientConfig(requestConfig, httpRequestRetryHandler, pcm);
    }

    private CloseableHttpClient getHttpClient(WxOfficialAccountCommonProperties wxConfig) {
        ClientConfig config = init(wxConfig);
        return HttpClients.custom()
                .setConnectionManager(config.pcm)
                .setRetryHandler(config.httpRequestRetryHandler)
                .setDefaultRequestConfig(config.requestConfig)
                .setConnectionManagerShared(true)
                .build();
    }

    public static String doGet(String url, Map<String, String> params, WxOfficialAccountCommonProperties wxConfig) {
        try {
            CloseableHttpClient httpClient = exec.getHttpClient(wxConfig);
            URIBuilder uriBuilder = new URIBuilder();
            if (params != null && params.size() > 0) {
                Set<String> keys = params.keySet();
                Iterator<String> it = keys.iterator();
                for (; it.hasNext(); ) {
                    String key = it.next();
                    String val = params.get(key);
                    uriBuilder.setParameter(key, val);
                }
            }
            URI uri = uriBuilder.build();
            HttpGet httpGet = new HttpGet(url + uri);
            return handleResponse(httpClient.execute(httpGet));
        } catch (Exception e) {
            log.error("url = {} , params = {} , error = {} ", url, JacksonUtils.generate(params), e);
        }
        return null;
    }

    /**
     * POST请求
     *
     * @param url
     * @param params
     * @return
     */
    public static String doPost(String url, Map<String, String> params,WxOfficialAccountCommonProperties wxConfig) {
        try {
            CloseableHttpClient httpClient = exec.getHttpClient(wxConfig);
            List<NameValuePair> paramList = new ArrayList<>();
            if (params != null && params.size() > 0) {
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    paramList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
            }
            HttpPost httpPost = new HttpPost(url);
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(paramList, UTF8_CHARSET));
            } catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
            }
            return handleResponse(httpClient.execute(httpPost));
        } catch (Exception e) {
            log.error("url = {} , params = {} , error = {} ", url, JacksonUtils.generate(params), e);
        }
        return null;
    }


    /**
     * 使用JSON方式请求
     *
     * @param url
     * @return
     */
    public static String postJSON(String url, Object params,WxOfficialAccountCommonProperties wxConfig) {
        try {
            CloseableHttpClient httpClient = exec.getHttpClient(wxConfig);
            HttpPost httpPost = new HttpPost(url);
            String requestBody = (params instanceof String) ? (String) params : JacksonUtils.generate(params);
            StringEntity myEntity = new StringEntity(requestBody, ContentType.APPLICATION_JSON);
            httpPost.setEntity(myEntity);
            return handleResponse(httpClient.execute(httpPost));
        } catch (Exception e) {
            log.error("url = {} , params = {} , error = {} ", url, JacksonUtils.generate(params), e);
        }
        return null;
    }

    /**
     * 使用JSON方式请求
     *
     * @param url
     * @return
     */
    public static String postXML(String url, Object params,WxOfficialAccountCommonProperties wxConfig) {
        CloseableHttpClient httpClient = exec.getHttpClient(wxConfig);
        try {
            HttpPost httpPost = new HttpPost(url);
            String requestBody = (params instanceof String) ? (String) params : JacksonUtils.generate(params);
            StringEntity myEntity = new StringEntity(requestBody, ContentType.create(
                    "application/xml", Consts.UTF_8));
            httpPost.setEntity(myEntity);
            return handleResponse(httpClient.execute(httpPost));
        } catch (Exception e) {
            log.error("url = {} , params = {} , error = {} ", url, JacksonUtils.generate(params), e);
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    public static String handleResponse(CloseableHttpResponse response) throws IOException {
        try {
            StatusLine statusCode = response.getStatusLine();
            if (statusCode.getStatusCode() >= 300) {
                throw new HttpResponseException(statusCode.getStatusCode(), statusCode.getReasonPhrase());
            }
            HttpEntity httpEntity = response.getEntity();
            if (httpEntity == null) {
                return null;
            }
            ContentType contentType = ContentType.getOrDefault(httpEntity);
            Charset charset = contentType.getCharset();
            if (charset == null) {
                charset = Consts.UTF_8;
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), charset));
            StringBuffer buffer = new StringBuffer();
            String line = null;
            for (; (line = reader.readLine()) != null; ) {
                buffer.append(line);
            }
            reader.close();
            return buffer.toString();
        } finally {
            response.close();
        }
    }

    class ClientConfig {
        public RequestConfig requestConfig;
        public HttpRequestRetryHandler httpRequestRetryHandler;
        public PoolingHttpClientConnectionManager pcm;

        public ClientConfig(RequestConfig requestConfig, HttpRequestRetryHandler httpRequestRetryHandler, PoolingHttpClientConnectionManager pcm) {
            this.requestConfig = requestConfig;
            this.httpRequestRetryHandler = httpRequestRetryHandler;
            this.pcm = pcm;
        }

    }



}


